/**
 * @author luzeyu & liangqiaowei
 */
package tiei.ajp.huffman.utils;

import java.io.*;
import java.util.*;

public class FileReader {
    //    int readIndex = -1;
    InputStream inputStream;
    Deque<Integer> queue;
    final int QUEUE_SIZE = 2;
    private int currentByte; // Data of current byte, -1 for EOF
    private int bitPosition; // Next bit index [0...7] to read from

    private int execSeq = 0;

    public static final int EOF = -1;
    public static final int BIT_O = 0;
    public static final int BIT_1 = 1;
    private static final int HSB_MASK = 0b10000000000000000000000000000000;
    private static final int LSB_MASK = 0b00000000000000000000000000000001;


    public FileReader(File infile) throws IOException {
        inputStream = new FileInputStream(infile);
        queue = new LinkedList<>();
        while (queue.size() < QUEUE_SIZE) {
            int data = inputStream.read();
//            readIndex++;
            queue.offer(data);
        }
    }

    public FileReader(InputStream is) throws IOException {
        inputStream = is;
        queue = new LinkedList<>();
        while (queue.size() < QUEUE_SIZE) {
            int data = inputStream.read();
//            readIndex++;
            queue.offer(data);
        }
    }

    private int readByte() throws IOException {
        int data = inputStream.read();
        // File empty, queue not empty
        if (data == -1 && queue.size() > 0) {
            return queue.poll();
        }
        // All empty
        if (data == -1 && queue.size() == 0) {
            return -1;
        }

        assert queue.size() == QUEUE_SIZE;

        queue.offer(data);
        return queue.poll();
    }

    /**
     * @return
     * @throws IOException
     * @note This func returns true at the `0` of the last leaf
     */
    private boolean checkIfTreeEnds() throws IOException {
//        int node = readByte();
        if (/*node == 0 && */(queue.getLast() != 0 && queue.getLast() != 1)) return true;
        return false;
    }

    /**
     * Used to read magic num
     *
     * @return
     * @throws IOException
     */
    public long readMagicNum() throws IOException {
        assert this.execSeq == 0;
        int k = readByte();
        long N = 0;
        for (int i = 0; i < k; i++) {
            int byteValue = readByte();
            N += (byteValue & 0xFF) * Math.pow(256, i);
        }
        execSeq++;
        return N;
    }

    /**
     * Read data of tree
     *
     * @return Serialized tree data
     * @throws IOException
     */
    public List<Byte> readTree() throws IOException {
        assert this.execSeq == 1;
//        byte b = (byte)readByte();
        List<Byte> data = new ArrayList<>();

        boolean isData = false;
        while (true) {
            byte b = (byte) readByte();
            data.add(b);
            if (b == 0 && !isData && checkIfTreeEnds()) break;
            if (isData) isData = false;    // Current byte is data, then next byte not data
            else {                        // Current byte not data, then must be either 0 or 1
                assert b == 0 || b == 1;
                if (b == 0) isData = true; // 0 indicates next byte is data
            }
        }
        // Add the data of the last leaf
        data.add((byte) readByte());
        execSeq++;
        return data;
    }

    /**
     * Used to read bit num of compressed
     *
     * @return
     * @throws IOException
     */
    public long readBitNum() throws IOException {
        assert this.execSeq == 2;
        int k = readByte();
        long N = 0;
        for (int i = 0; i < k; i++) {
            int byteValue = readByte();
            N += (byteValue & 0xFF) * Math.pow(256, i);
        }
        execSeq++;
        return N;
    }

    /**
     * Used to read compressed data
     *
     * @return
     * @throws IOException
     */
    public int readData() throws IOException {
        assert execSeq == 3;
        return readByte();
    }

    boolean firstRun = true;

    public int readBit() throws IOException {
        assert execSeq == 3;
        if (firstRun) {
            firstRun = false;
            currentByte = readByte();
        }

        if (currentByte == EOF) {
            return EOF;
        }

        int bit = (currentByte << bitPosition) & 0b10000000;
        bitPosition++;

        // Check if read full byte
        if (bitPosition == 8) { // 读取完一个字节
            currentByte = readByte();
            bitPosition = 0;
        }

//        bitCounter ++;
        assert bit == 0b00000000 || bit == 0b10000000;
        return bit == 0 ? BIT_O : BIT_1;
    }

    // Test
//    public static void main(String[] args) {
//        String filePath = "input.txt";
//        try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(filePath))) {
//            int data;
//            while ((data = inputStream.read()) != -1) {
//                System.out.print((char) data);  // 逐字节读取并转换为字符输出
//            }
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
}
